// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: tu@tulrich.com (Thatcher Ulrich)

// Main program for desktop OpenGL version of the WebGL benchmarks.
// May be useful for identifying WebGL bottlenecks.

#include <GL/glew.h>
#include <GL/freeglut.h>
#include <math.h>
#include <stdio.h>
#include <string>
#include <string.h>
#include <sys/timeb.h>
#include <vector>
#include "glmath.h"
#include "webgl_bench.h"

using std::string;
using std::vector;

static const unsigned int WIN_WIDTH = 640;
static const unsigned int WIN_HEIGHT = 480;
static const float FOVY = M_PI / 3.0f;  // radians
static const float Z_NEAR = 0.1f;
static const float Z_FAR = 20.0f;

void Display();
void reshape(int, int);
void GenerateReport();
void ShowReport();

static RenderInfo s_ri;
static Bench s_bench;
static bool s_generated_report = false;

static const char* s_vertexshader_t1 =
  "// Minimal shader for testing pure diffuse texture fill rate.\n"
  "uniform mat4 MVP;\n"
  "\n" 
  "attribute vec3 position;\n"
  "attribute vec2 t0_uv;\n"
  "\n"
  "varying vec2 uv;\n"
  "\n"
  "void main() {\n"
  "  gl_Position = MVP * vec4(position, 1.0);\n"
  "  uv = t0_uv;\n"
  "}\n";

static const char* s_fragmentshader_t1 =
  "uniform sampler2D t0_sampler;\n"
  "varying vec2 uv;\n"
  "\n"
  "void main() {\n"
  "  gl_FragColor = texture2D(t0_sampler, uv);\n"
  "}\n";

GLuint LoadShader(const char* shader_string, GLenum type) {
  GLuint shader_id = glCreateShader(type);
  if (!shader_id) return 0;
  const char* strings[1] = { shader_string };
  glShaderSource(shader_id, 1, strings, NULL);
  glCompileShader(shader_id);
  GLint status = 0;
  glGetShaderiv(shader_id, GL_COMPILE_STATUS, &status);
  if (!status) {
    vector<GLchar> info_log;
    int info_log_length = 0;
    glGetShaderiv(s_ri.program, GL_INFO_LOG_LENGTH, &info_log_length);
    info_log.resize(info_log_length);
    glGetShaderInfoLog(shader_id, info_log_length, NULL, &info_log[0]);
    fprintf(stderr, "Error compiling shader: %s\n", &info_log[0]);
    return 0;
  }
  CheckGlError("load_shader");
  return shader_id;
}

// Return true on success.
bool Init() {
  s_ri.vshader = LoadShader(s_vertexshader_t1, GL_VERTEX_SHADER);
  if (!s_ri.vshader) return false;
 
  s_ri.fshader = LoadShader(s_fragmentshader_t1, GL_FRAGMENT_SHADER);
  if (!s_ri.fshader) return false;
 
  s_ri.program = glCreateProgram();
  if (!s_ri.program) return false;
 
  glAttachShader(s_ri.program, s_ri.vshader);
  glAttachShader(s_ri.program, s_ri.fshader);
 
  glBindAttribLocation(s_ri.program, 0, "position");
  //  gl.bindAttribLocation(program, 1, 'texcoord_in');
 
  glLinkProgram(s_ri.program);
 
  CheckGlError("init_gl 1");

  GLint status = 0;
  glGetProgramiv(s_ri.program, GL_LINK_STATUS, &status);
  if (!status) {
    int info_log_length = 0;
    vector<GLchar> info_log;
    glGetProgramiv(s_ri.program, GL_INFO_LOG_LENGTH, &info_log_length);
    info_log.resize(info_log_length);
    glGetProgramInfoLog(s_ri.program, info_log_length, NULL, &info_log[0]);
    fprintf(stderr, "Error in program linking: %s", &info_log[0]);
    return false;
  }
 
  glUseProgram(s_ri.program);
 
  CheckGlError("init_gl 2");
 
  s_ri.mvp_location = glGetUniformLocation(s_ri.program, "MVP");
 
  CheckGlError("init_gl 3");
 
  glUniform1i(glGetUniformLocation(s_ri.program, "t0_sampler"), 0);
 
  CheckGlError("init_gl 4");
 
  //reshape(canvas, canvas.clientWidth, canvas.clientHeight);
 
  s_ri.position_location = glGetAttribLocation(s_ri.program, "position");
  glEnableVertexAttribArray(s_ri.position_location);
  s_ri.t0_uv_location = glGetAttribLocation(s_ri.program, "t0_uv");
  glEnableVertexAttribArray(s_ri.t0_uv_location);
 
  glClearColor(0, 0, 0, 1);
  glClearDepth(1.0);

  s_bench.Init(4, 64, BENCH_TRIANGLE_STRIP);

  // Put the camera in a standard place.
  float pos[3] = { 2.3f, 0, 0 };
  float dir[3] = { -1, 0, 0 };
  float up[3] = { 0, 1, 0 };
  MakeOrientationAffine(pos, dir, up, s_ri.camera_orient);

  return true;
}

void Draw() {
  MyGluPerspective(FOVY, float(WIN_WIDTH) / float(WIN_HEIGHT),
                   Z_NEAR, Z_FAR, s_ri.projection_matrix);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LEQUAL);

  s_bench.Draw(&s_ri);

  CheckGlError("Draw");
}

int main(int argc, char* argv[]) {
  // glut
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_ALPHA);
  glutInitWindowPosition(100, 100);
  glutInitWindowSize(WIN_WIDTH, WIN_HEIGHT);
  glutCreateWindow("WebGL benchmark using desktop OpenGL");

  // GLEW
  GLenum err = glewInit();
  if (GLEW_OK != err) {
    fprintf(stderr, "Error in glewInit: %s\n", glewGetErrorString(err));
    fflush(stderr);
    return 1;
  }

  if (!glewIsSupported("GL_VERSION_2_0")) {
    fprintf(stderr, "Error -- this program requires OpenGL 2.0 support.\n");
    fflush(stderr);
    return 1;
  }

  glEnable(GL_DEPTH_TEST);

  // Initialize state.
  if (!Init()) {
    fprintf(stderr, "Error -- couldn't initialized GL state.\n");
    fflush(stderr);
    return 1;
  }

  glutDisplayFunc(Display);

  glutMainLoop();
  return 0;
}

void Display() {
  Draw();
  glutSwapBuffers();
  CheckGlError("Display");

  if (!s_generated_report) {
    s_generated_report = true;
    GenerateReport();
  }
}

struct Report {
  int layers;
  int dim;
  PrimType prim;
  time_t dt;
  int tps;
  int dps;
};

vector<Report> s_report;

#ifdef _WIN32
#include <windows.h>
time_t GetTimeMillis() {
  LARGE_INTEGER	li;
  QueryPerformanceCounter(&li);

  LARGE_INTEGER	freq;
  QueryPerformanceFrequency(&freq);
  
  double milliseconds = double(li.QuadPart) * 1000.0 / double(freq.QuadPart);
  return static_cast<int>(floor(milliseconds + 0.5));
}
#else
// TODO use gettimeofday()
#endif

void GenerateReport() {
  // Put the camera in a standard place.
  float pos[3] = { 2.3f, 0, 0 };
  float dir[3] = { -1, 0, 0 };
  float up[3] = { 0, 1, 0 };
  MakeOrientationAffine(pos, dir, up, s_ri.camera_orient);

  s_report.resize(0);

  struct TestParams {
    int layers;
    int dim;
    PrimType prim;
  } tests_params[] = {
    { 4,  16, BENCH_TRIANGLE_STRIP },
    { 4,  32, BENCH_TRIANGLE_STRIP },
    { 4,  64, BENCH_TRIANGLE_STRIP },
    { 8,  64, BENCH_TRIANGLE_STRIP },
  };

  for (int seq = 0; seq < sizeof(tests_params) / sizeof(tests_params[0]); seq++) {
    const TestParams& params = tests_params[seq];

    s_bench.Init(params.layers, params.dim, params.prim);
 
    // Warm up.
    for (int i = 0; i < 10; i++) {
      Draw();
    }
 
    // Do a bunch of draws -- run for at least two seconds.
    s_ri.tri_count = 0;
    s_ri.draw_count = 0;
    time_t start = GetTimeMillis();
    for (;;) {
      for (int i = 0; i < 10; i++) {
        Draw();
      }
      time_t now = GetTimeMillis();
      if (now - start > 2000) {
        break;
      }
    }
 
    time_t end = GetTimeMillis();
    time_t dt = end - start;
 
    // Compute rates.
    int tps = static_cast<int>(
        floorf(1000.0f * s_ri.tri_count / float(dt) + 0.5));
    int dps = static_cast<int>(
        floorf(1000.0f * s_ri.draw_count / float(dt) + 0.5));

    const Report report = {
      params.layers,
      params.dim,
      params.prim,
      dt,
      tps,
      dps
    };
    s_report.push_back(report);
  }

  ShowReport();
}

#ifdef _WIN32
#define PLATFORM "win32"
#define snprintf _snprintf
#else
// TODO: identify Mac separately
#define PLATFORM "posix"
#endif
 
string Pad(const string& x, int width) {
  string text = x;
  while (text.size() < width) {
    text = " " + text;
  }
  return text;
}

string Pad(const char* x, int width) {
  return Pad(string(x), width);
}

string Pad(int x, int width) {
  char buf[1000];
  snprintf(buf, sizeof(buf), "%d", x);
  return Pad(string(buf), width);
}

string Pad(time_t x, int width) {
  char buf[1000];
  snprintf(buf, sizeof(buf), "%u", x);
  return Pad(string(buf), width);
}

string Pad(double x, int width) {
  char buf[1000];
  snprintf(buf, sizeof(buf), "%g", x);
  return Pad(string(buf), width);
}

string Pad(float x, int width) {
  return Pad(static_cast<double>(x), width);
}

void ShowReport() {
  printf("platform = %s \n", PLATFORM);
  printf("GL_VERSION = %s\n", glGetString(GL_VERSION));
  printf("GL_VENDOR = %s\n", glGetString(GL_VENDOR));
  printf("GL_RENDERER = %s\n", glGetString(GL_RENDERER));
  printf("GL_SHADING_LANGUAGE_VERSION = %s\n",
    glGetString(GL_SHADING_LANGUAGE_VERSION));
  printf("+--------+-----+-------+------------+-----------+---------+\n");
  printf("| layers | dim | prim  |  tris/sec  | draws/sec |   dt    |\n");
  printf("+--------+-----+-------+------------+-----------+---------+\n");
  for (int i = 0; i < s_report.size(); i++) {
    const Report& rep = s_report[i];
    string line = "| " + Pad(rep.layers, 6) + " | " + Pad(rep.dim, 3) + " | " +
                  Pad(rep.prim == BENCH_TRIANGLE_STRIP ? "strip" : "tris", 5) +
                  " | " + Pad(rep.tps, 10) + " | " + Pad(rep.dps, 9) + " | " +
                  Pad(rep.dt / 1000.0, 7) + " |\n"; 
   printf(line.c_str());
  }
  printf("+--------+-----+-------+------------+-----------+---------+\n");
  fflush(stdout);
}
